/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.TypeParameterMatcher;


public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {

    // 类型匹配器，提取实现类 实现的 真实泛型，创建出一个 ReflectiveMatcher 对象。
    private final TypeParameterMatcher matcher;
    // 是否偏向使用堆外内存，一般是 true
    private final boolean preferDirect;

    protected MessageToByteEncoder() {
        this(true);
    }

    protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
        this(outboundMessageType, true);
    }

    protected MessageToByteEncoder(boolean preferDirect) {
        // 提取当前对象的 真实 泛型，比如说 BusinessEncoder extends MessageToByteEncoder<User> {...}
        // 这里就会拿 User.class 创建一个 matcher 对象。
        matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }

    protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
        matcher = TypeParameterMatcher.get(outboundMessageType);
        this.preferDirect = preferDirect;
    }

    public boolean acceptOutboundMessage(Object msg) throws Exception {
        return matcher.match(msg);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            // 条件成立：说明当前 编码器 可以对msg对象 进行编码。
            if (acceptOutboundMessage(msg)) {
                // 转成 真实的 类型
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                // 到内存池申请 byteBuf，一般会重写 allocateBuffer 这个方法，按照 业务自身的msg对象大小去 申请 byteBuf
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
                    encode(ctx, cast, buf);
                } finally {
                    ReferenceCountUtil.release(cast);
                }

                // 条件成立：说明 编码 成功，buf内是有 有效数据的，需要向下一个 出站处理器 传递
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
                    // 执行到这里，说明 编码 方法 未向 byteBuf 内写任何东西... 说明没有啥东西 需要向 socket 写...
                    // 这里将 从内存池申请的 byteBuf 释放。
                    buf.release();
                    // 传递一个空的 buffer。
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }

                // buf 置为null。
                buf = null;

            } else {
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            // 条件什么时候成立呢？  正常情况 buf 是 null， 只有 上面逻辑出现 异常，担心 内存泄露，才会执行 buf.release().
            if (buf != null) {
                buf.release();
            }
        }
    }

    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
                               boolean preferDirect) throws Exception {
        if (preferDirect) {
            return ctx.alloc().ioBuffer();
        } else {
            return ctx.alloc().heapBuffer();
        }
    }

    protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;

    protected boolean isPreferDirect() {
        return preferDirect;
    }
}
